﻿
using System;
using System.Text.RegularExpressions;

namespace Test30.Engine
{
    public class Roller
    {
        
        public float Average
        {
            get
            {
                float average = _average;
                if (_nextRoller != null) average += _nextRoller.Average;
                return average;
            }
        }
        public bool IsFixed
        {
            get
            {
                bool isFixed = _isFixed;
                if (_nextRoller != null) isFixed &= _nextRoller.IsFixed;
                return isFixed;
            }
        }

        private Roller(Func<int> rollFunction, float average, string text, bool isFixed = false)
        {
            _rollFunction = rollFunction;
            _average = average;
            _text = text;
            _isFixed = isFixed;
        }

        public int Roll()
        {
            int result = _rollFunction();
            if (_nextRoller != null) result += _nextRoller.Roll();

            return result;
        }

        #region static api

        public static Roller Parse(string text)
        {
            text = text.Trim();
            if (_parser == null)
            {
                string pattern = @"^((?<die>(?<dice>\d+)d(?<sides>\d+))|(?<tri>(?<center>\d+)t(?<range>\d+))|(?<range>(?<min>\d+)-(?<max>\d+))|(?<fixed>(?<value>-?\d+)))(?<taper>\((?<chance>\d+)\:(?<outof>\d+)\))?$";
                _parser = new Regex(pattern, RegexOptions.Compiled | RegexOptions.ExplicitCapture);
            }
            Match match = _parser.Match(text);
            if (!match.Success) return null;
            Roller roller;
            if (match.Groups["die"].Success)
            {
                int dice = Int32.Parse(match.Groups["dice"].Value);
                int sides = Int32.Parse(match.Groups["sides"].Value);
                roller = Dice(dice, sides);
            }
            else if (match.Groups["tri"].Success)
            {
                int center = Int32.Parse(match.Groups["center"].Value);
                int range = Int32.Parse(match.Groups["range"].Value);
                roller = Triangle(center, range);
            }
            else if (match.Groups["range"].Success)
            {
                int min = Int32.Parse(match.Groups["min"].Value);
                int max = Int32.Parse(match.Groups["max"].Value);
                roller = Range(min, max);
            }
            else // fixed
            {
                int value = Int32.Parse(match.Groups["value"].Value);
                roller = Fixed(value);
            }

            if (match.Groups["taper"].Success)
            {
                int chance = Int32.Parse(match.Groups["chance"].Value);
                int outOf = Int32.Parse(match.Groups["outof"].Value);

                roller._nextRoller = Taper(chance, outOf);
            }

            return roller;
        }

        public static Roller Dice(int dice, int sides)
        {
            return new Roller(
                () => Rng.Roll(dice, sides),
                dice * ((1 + sides) / 2.0f), dice.ToString() + "d" + sides.ToString());
        }
        public static Roller Range(int min, int max)
        {
            return new Roller(
                () => Rng.IntInclusive(min, max),
                (min + max) / 2.0f, min.ToString() + "-" + max.ToString());
        }
        public static Roller Fixed(int value)
        {
            return new Roller(() => value, value, value.ToString(), true);
        }

        public static Roller Triangle(int center, int range)
        {
            return new Roller(
                () => Rng.TriangleInt(center, range),
                center, center.ToString() + "t" + range.ToString());
        }
        public static Roller Taper(int chance, int outOf)
        {
            return new Roller(
                () => Rng.Taper(0, 1, chance, outOf),
                chance / (outOf - (float)chance),
                "(" + chance + ":" + outOf + ")");
        }
        #endregion

        public override string ToString()
        {
            string text = _text;
            if (_nextRoller != null) text += _nextRoller.ToString();

            return text;
        }

        private static Regex _parser;
        private readonly Func<int> _rollFunction;
        private readonly float _average;
        private readonly string _text;
        private readonly bool _isFixed;

        private Roller _nextRoller;
    }
}
